package heap

import (
	"jvmgo/classfile"
)

type Method struct {
	ClassMember
	maxStack                uint16
	maxLocals               uint16
	code                    []byte
	argSlotCount            uint
	exceptionTable          ExceptionTable
	lineNumberTable         *classfile.LineNumberTableAttribute
	exceptions              *classfile.ExceptionsAttribute
	parameterAnnotationData []byte
	annotationDefaultData   []byte
	parsedDescriptor        *MethodDescriptor
}

func newMethods(class *Class, cfMethods []*classfile.MemberInfo) []*Method {
	methods := make([]*Method, len(cfMethods))
	for i, cfMethods := range cfMethods {
		methods[i] = newMethod(class, cfMethods)

	}
	return methods
}

func newMethod(class *Class, cfMethod *classfile.MemberInfo) *Method {
	m := &Method{}
	m.class = class
	m.copyMemberInfo(cfMethod)
	m.copyAttributes(cfMethod)
	md := parseMethodDescriptor(m.descriptor)
	m.parsedDescriptor = md
	m.calcArgSlotCount(md.parameterTypes)
	if m.IsNative() {
		m.injectCodeAttribute(md.returnType)
	}
	return m
}

func (self *Method) injectCodeAttribute(s string) {
	self.maxStack = 4
	self.maxLocals = uint16(self.argSlotCount)
	switch s[0] {
	case 'V':
		self.code = []byte{0xfe, 0xb1} // return
	case 'D':
		self.code = []byte{0xfe, 0xaf} // dreturn
	case 'F':
		self.code = []byte{0xfe, 0xae} //freturn
	case 'J':
		self.code = []byte{0xfe, 0xad} //lreturn
	case 'L', '[':
		self.code = []byte{0xfe, 0xb0} //areturn
	default:
		self.code = []byte{0xfe, 0xac} // ireturn
	}

}
func (self *Method) calcArgSlotCount(paramTypes []string) {
	//parsedDescriptor := parseMethodDescriptor(self.descriptor)
	for _, paramType := range paramTypes {
		self.argSlotCount++
		if paramType == "J" || paramType == "D" {
			self.argSlotCount++
		}
	}
	if !self.IsStatic() {
		self.argSlotCount++
	}

}

func (self *Method) copyAttributes(cfMethod *classfile.MemberInfo) {
	if codeAttr := cfMethod.CodeAttribute(); codeAttr != nil {
		self.maxStack = codeAttr.MaxStack()
		self.maxLocals = codeAttr.MaxLocals()
		self.code = codeAttr.Code()
		self.lineNumberTable = codeAttr.LineNumberTableAttribute()
		self.exceptionTable = newExceptionTable(codeAttr.ExceptionTable(), self.class.constantPool)
	}
	self.exceptions = cfMethod.ExceptionsAttribute()
	self.annotationData = cfMethod.RuntimeVisibleAnnotationsAttributeData()
	self.parameterAnnotationData = cfMethod.RuntimeVisibleParameterAnnotationsAttributeData()
	self.annotationDefaultData = cfMethod.AnnotationDefaultAttributeData()
}

func (self *Method) FindExceptionHandler(extClass *Class, pc int) int {
	handler := self.exceptionTable.findExceptionHandler(extClass, pc)
	if handler != nil {
		return handler.handlerPc
	}
	return -1
}

func (self *Method) IsStatic() bool {
	return (self.accessFlags & ACC_STATIC) != 0
}

func (self *Method) MaxLocals() uint {
	return uint(self.maxLocals)
}

func (self *Method) MaxStack() uint {
	return uint(self.maxStack)
}

func (self *Method) Code() []byte {
	return self.code
}

func (self *Method) Class() *Class {
	return self.class
}

func (self *Method) Name() string {
	return self.name
}

func (self *Method) Descriptor() string {
	return self.descriptor
}

func (self *Method) ArgSlotCount() uint {
	return self.argSlotCount
}

func (self *Method) IsAbstract() bool {
	return 0 != self.accessFlags&ACC_ABSTRACT
}

func (self *Method) IsNative() bool {
	return 0 != self.accessFlags&ACC_NATIVE
}

func (self *Method) Loader() *ClassLoader {
	return self.class.loader

}

func (self *Method) GetLineNumber(pc int) int {
	if self.IsNative() {
		return -2
	}
	if self.lineNumberTable == nil {
		return -1
	}
	return self.lineNumberTable.GetLineNumber(pc)

}

func (self *Method) isConstructor() bool {
	return !self.IsStatic() && self.name == "<init>"
}

func (self *Method) ParameterTypes() []*Class {
	if self.argSlotCount == 0 {
		return nil
	}
	paramTypes := self.parsedDescriptor.parameterTypes
	paramClasses := make([]*Class, len(paramTypes))
	for i, paramType := range paramTypes {
		paramClassName := toClassName(paramType)
		paramClasses[i] = self.class.loader.LoadClass(paramClassName)
	}
	return paramClasses
}

func (self *Method) ExceptionTypes() []*Class {
	if self.exceptions == nil {
		return nil
	}
	exIndexTable := self.exceptions.ExceptionIndexTable()
	exClasses := make([]*Class, len(exIndexTable))
	cp := self.class.constantPool
	for i, exIndex := range exIndexTable {
		classRef := cp.GetConstant(uint(exIndex)).(*ClassRef)
		exClasses[i] = classRef.ResolvedClass()
	}
	return exClasses
}

func (self *Method) AccessFlags() uint16 {
	return self.accessFlags
}

func (self *Method) ParameterAnnotationData() []byte {
	return self.parameterAnnotationData
}
func (self *Method) AnnotationDefaultData() []byte {
	return self.annotationDefaultData

}

func (self *Method) isClinit() bool {
	return self.IsStatic() && self.name == "<clinit>"
}

func (self *Method) ReturnType() *Class {
	returnType := self.parsedDescriptor.returnType
	returnClassName := toClassName(returnType)
	return self.class.loader.LoadClass(returnClassName)
}

var (
	_shimClass    = &Class{name: "~shim"}
	_returnCode   = []byte{0xb1}
	_athrowCode   = []byte{0xbf}
	_returnMethod = &Method{
		ClassMember: ClassMember{
			accessFlags: ACC_STATIC,
			name:        "<return>",
			class:       _shimClass,
		},
		code: _returnCode,
	}
	_athrowMethod = &Method{
		ClassMember: ClassMember{
			accessFlags: ACC_STATIC,
			name:        "<athrow>",
			class:       _shimClass,
		},
		code: _athrowCode,
	}
)

func ShimReturnMethod() *Method {
	return _returnMethod
}
